#ifndef EVENT_SYSTEM_H
#define EVENT_SYSTEM_H

#include <concrete/Component.h>

class Event
{
public:
	virtual String getID() const = 0;
	//You should NOT override this yourself
	virtual void   onRaise() = 0;
};

class LocalEvent : public Event
{
public:
	virtual String getID() const = 0;
private:
	virtual void   onRaise() {}
};

class Response
{
public:
	virtual void respond( const Event& event ) = 0;
};

//Inherit your responses from this
template< class T_Event >
class ResponseTo : public Response
{
public:
	virtual void respond( const Event& event )
	{
		T_Event* pCastEvent = dynamic_cast< T_Event* >( &event );
		if ( !pCastEvent ) {
			std::cerr << "Error in Response< T_Event >::respond(const Event&): \
Attempting to respond to the wrong event type. Bailing." << std::endl;
			return;
		}
		this->respond( *pCastEvent );
	}
	
	//Implement this in your derived class
	virtual void respond( const T_Event& event ) = 0;
};

//A response to an event that simply raises another event
//TODO: Consider thinking of a better name for this class
template< class T >
class EventToEventResponse : public ResponseTo< T >
{
	Event* m_pNextEvent;
public:
	//Constructor
	EventToEventResponse( const T& event ) : m_pNextEvent( new T(event); ) {}
	//Copy constructor
	EventToEventResponse( const EventToEventResponse<T>& source )
	{
		m_pNextEvent = new T( *source.m_pNextEvent );
	}
	//Assignment operator
	EventToEventResponse<T>& operator=( const EventToEventResponse<T>& source )
	{
		T* pTmpEvent = new T( *source.m_pNextEvent );
		delete m_pNextEvent;
		m_pNextEvent = pTmpEvent;
	}
	//Destructor
	virtual ~EventToEventResponse()
	{
		delete m_pNextEvent;
	}
	
	virtual void respond( const T& event )
	{
		EventManager->raiseEvent( *m_pNextEvent );
	}
}

//There would (probably) be one instance of this as a member of Engine (or 
// alternatively as a singleton).
class EventManager
{
	typedef std::list< Response* > ResponseList;
	//NOTE: Using Event as map key (need to work out how best to do this)
	//      In fact, this whole code is basically *only* using Event as an
	//      identifier.
	typedef std::map< String, ResponseList > RuleMap;
	RuleMap m_RuleMap;
	
	//std::queue< Event > m_EventQueue;
public:
	
	~EventManager()
	{
		RuleMap::iterator itRuleList;
		for ( itRuleList = m_RuleMap.begin();
		      itRuleList != m_RuleMap.end();
			  ++itRuleList ) {
			while ( itRuleList->second.size() ) {
				delete itRuleList->second.back();
				itRuleList->second.pop_back();
			}
		}
	}
	
	//NOTE: The more general term for this would be addHandler( )
	template< class T_EventType >
	void addRule( const T_EventType& e, const ResponseTo<T_EventType>& response )
	{
		//This takes the event ID and maps it to a NEW instance of a response
		// that is constructed using the given response
		m_RuleMap[ e.getID() ].push_back( new ResponseTo<T_EventType>(response) );
	}
	
	/*void enqueueEvent( const Event* e )
	{
		m_EventQueue.push( e );
	}*/
	//I think I prefer this method actually
	void raiseEvent( const Event& e )
	{
		//m_EventQueue.push( e );
		
		//Raise immediately
		ResponseList& responses = m_RuleMap[ e.getID() ];
		ResponseList::iterator itResponse;
		//For each response mapped to this event
		for ( itResponse = responses.begin();
		      itResponse != responses.end();
			  ++itResponse ) {
			(*itResponse)->respond( e );
		}
	}
	
	//Not using this any more as events will be raised immediately
	//Called in mainloop
	/*void update()
	{
		while m_EventQueue not empty {
			Event& e = m_EventQueue.front();
			e.
			for each Rule r in m_RuleMap[e]
				r.invoke();
			m_EventQueue.pop();
		}
	}*/
	
};

//A set of rules, added to the game during initialisation
class RuleSet : public Component<RuleSet>
{
public:
	virtual void addRulesTo( EventManager* pEventMgr ) = 0;
	
	//TODO: Consider using these instead to make proper usage more obvious
	/*
	virtual std::list<[rule\event+response pair]> getRulesToAdd() = 0;
	void addRulesTo( EventManager* pEventMgr )
	{
		//TODO: Add each rule in this->getRulesToAdd() to pEventMgr.
	}
	*/
};

#endif
